package cn.okcode.slowjson.parser;

import cn.okcode.slowjson.Config;
import cn.okcode.slowjson.ast.*;
import cn.okcode.slowjson.internal.BeanUtils;
import cn.okcode.slowjson.internal.JsonException;
import cn.okcode.slowjson.internal.TypeCastUtils;
import cn.okcode.slowjson.internal.TypeUtils;

import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.*;

/**
 * 对象转换为json对象
 *
 * @author pyk13
 */
public class JsonNodeWriter {

    public AbstractNode<?> write(Object object) {
        AbstractNode<?> node;
        if (object == null) {
            return new NullNode();
        }
        if (object instanceof AbstractNode) {
            return (AbstractNode<?>) object;
        }
        Class<?> clazz = object.getClass();
        if (object instanceof Map) {
            node = write((Map) object);
        } else if (object instanceof Collection) {
            node = write((Collection<?>) object);
        } else if (clazz.isArray()) {
            node = writeArray(object);
        } else {
            node = writePrimary(object);
        }
        return node;
    }

    public AbstractNode<?> writePrimary(Object object) {
        if (object == null) {
            return new NullNode();
        }
        AbstractNode<?> node;
        Class<?> clazz = object.getClass();
        String className = clazz.getName();
        switch (className) {
            case "byte":
            case "java.lang.Byte":
            case "short":
            case "java.lang.Short":
            case "int":
            case "java.lang.Integer":
            case "long":
            case "java.lang.Long":
                node = write(TypeCastUtils.castToLong(object));
                break;
            case "float":
            case "java.lang.Float":
            case "java.lang.Double":
            case "double":
                node = write(TypeCastUtils.castToDouble(object));
                break;
            case "java.math.BigDecimal":
                node = write(TypeCastUtils.castToBigDecimal(object));
                break;
            case "java.lang.Boolean":
            case "boolean":
                node = write(TypeCastUtils.castToBoolean(object));
                break;
            case "java.sql.Timestamp":
                node = write(TypeCastUtils.castToTimestamp(object));
                break;
            case "java.util.Date":
                node = write(TypeCastUtils.castToDate(object));
                break;
            case "char":
            case "java.lang.Character":
            case "java.lang.String":
                node = write(TypeCastUtils.castToString(object));
                break;
            default:
                node = writeObject(object);
                break;
        }
        return node;
    }

    public ObjectNode writeObject(Object object) {
        Objects.requireNonNull(object, "对象参数不能为空");
        List<Field> fields = TypeUtils.getFields(object.getClass());

        Map<PropertyNode, AbstractNode<?>> properties = new HashMap<>(fields.size());
        for (Field field : fields) {
            String propName = field.getName();
            Object value = BeanUtils.getValue(field, object);

            PropertyNode propNode = new PropertyNode(propName);
            AbstractNode valueNode = write(value);
            properties.put(propNode, valueNode);
        }
        return new ObjectNode(properties);
    }

    public ArrayNode writeArray(Object value) {
        int length = Array.getLength(value);
        List<AbstractNode<?>> childNodes = new ArrayList<>(length);
        for (int i = 0; i < length; i++) {
            Object element = Array.get(value, i);
            childNodes.add(write(element));
        }
        return new ArrayNode(childNodes);
    }

    public ObjectNode write(Map map) {
        Map<PropertyNode, AbstractNode<?>> properties = new HashMap<>(map.size());
        for (Object objEntry : map.entrySet()) {
            Map.Entry entry = (Map.Entry) objEntry;
            if (!(entry.getKey() instanceof String)) {
                throw new JsonException("Map的Key必须是String类型");
            }
            String prop = (String) entry.getKey();
            PropertyNode propNode = new PropertyNode(prop);
            AbstractNode valueNode = write(entry.getValue());
            properties.put(propNode, valueNode);
        }
        return new ObjectNode(properties);
    }

    public ArrayNode write(Collection<?> collection) {
        List<AbstractNode<?>> childNodes = new ArrayList<>(collection.size());
        for (Object obj : collection) {
            childNodes.add(write(obj));
        }
        return new ArrayNode(childNodes);
    }

    public BoolNode write(Boolean value) {
        return new BoolNode(value);
    }

    public NumberNode write(Number value) {
        if (value instanceof BigDecimal) {
            return new NumberNode(value.doubleValue());
        } else {
            return new NumberNode(value);
        }
    }

    public StringNode write(Date value) {
        DateFormat format = new SimpleDateFormat(Config.DEFAULT_DATETIME_FORMAT);
        return new StringNode(format.format(value));
    }

    public StringNode write(String value) {
        return new StringNode(value);
    }
}
